package com.ibeeking.found.common.utils;

import com.ibeeking.found.common.annotation.CompareProperty;
import com.ibeeking.found.common.response.CompareVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.Data;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.*;

/**
 * @ClassName CompareJsonUtils
 * @Description 请描述类的业务用途
 * @Author ibeeking
 * @Date 2021/10/27 09:57
 **/
public class CompareJsonUtils {

    private static final String ADD = "新增";
    private static final String UPDATE = "修改";
    private static final String DELETE = "删除";
    private static final String SPLIT = ">";

    public static List<CompareVo> compareJsonIterator(JSONObject json1, JSONObject json2, String path, Class clazz) {
        Map<String, Object> map = new HashMap<>();
        compareJsonIterator(json1, json2, 0, 0, null, path, clazz, map);
        List<CompareVo> compareVoList = new ArrayList<>();
        for (Object o : map.values()) {
            compareVoList.add((CompareVo) o);
        }
        compareVoList.sort(Comparator.comparing(CompareVo::getSort));
        return compareVoList;
    }

    /**
     * 数据比对迭代器
     * @param json1
     * @param json2
     * @param sort
     * @param path
     * @param clazz
     * @param data
     */
    private static void compareJsonIterator(JSONObject json1, JSONObject json2, Integer sort, Integer index, String names, String path, Class clazz, Map<String, Object> data) {
        Set<String> keys = new HashSet<>();
        if (json1 != null) {
            keys.addAll(json1.keySet());
        }
        if (json2 != null) {
            keys.addAll(json2.keySet());
        }
        for (String key : keys) {
            compareJsonSplit(json1, json2, sort, index, names, path, clazz, key, data);
        }
    }

    /**
     * 判断数据类型
     * @param json1
     * @param json2
     * @param sort
     * @param path
     * @param clazz
     * @param key
     * @param data
     */
    private static void compareJsonSplit(JSONObject json1, JSONObject json2, Integer sort, Integer index, String names, String path, Class clazz, String key, Map<String, Object> data) {
        if (sort != null && index != null) {
            sort = sort * (path.split("\\.").length + 1) + index;
        }
        if (index != null && index != 0) {
            path = path + String.format("[%s]", index);
        }
        if (StringUtils.isEmpty(path)) {
            path = key;
        } else {
            path = path + "." + key;
        }
        if (StringUtils.isEmpty(names)) {
            names = getFieldNamePath(index, path, clazz);
        } else {
            String namePath = getFieldNamePath(index, path, clazz);
            if (namePath != null) {
                if (names.lastIndexOf(SPLIT) >= 0) {
                    names = names.substring(0, names.lastIndexOf(SPLIT));
                    String[] nps = namePath.split(SPLIT);
                    if (nps.length > 1) {
                        for (int i = 0; i < nps.length; i++) {
                            if (i >= nps.length - 2) {
                                names = names + SPLIT + nps[i];
                            }
                        }
                    } else {
                        names = namePath;
                    }
                } else {
                    names = namePath;
                }
            } else {
                names = null;
            }
        }
        if (json1 != null && json2 != null && json1.get(key) instanceof JSONObject && json2.get(key) instanceof JSONObject) {
            //Map<String, Object> map = new HashMap<>();
            //compareJsonIterator((JSONObject) json1.get(key), (JSONObject) json2.get(key), null, path, clazz, map);
            //data.put(key, map);
            compareJsonIterator((JSONObject) json1.get(key), (JSONObject) json2.get(key), sort, 0, names, path, clazz, data);
        } else if (json1 != null && json2 != null && json1.get(key) instanceof JSONArray && json2.get(key) instanceof JSONArray) {
            //Map<String, Object> map = new HashMap<>();
            JSONArray j1 = ((JSONArray) json1.get(key));
            JSONArray j2 = ((JSONArray) json2.get(key));
            int jsCount1 = j1.size();
            int jsCount2 = j2.size();
            int count = jsCount1 > jsCount2 ? jsCount1 : jsCount2;
            for (int i = 0; i < count; i++) {
                if (j1.size() > i && j2.size() > i && j1.get(i) != null && j2.get(i) != null) {
                    //compareJsonIterator((JSONObject) j1.get(i), (JSONObject) j2.get(i), i, path, clazz, map);
                    compareJsonIterator((JSONObject) j1.get(i), (JSONObject) j2.get(i), sort, i + 1, names, path, clazz, data);
                } else if (j1.size() > i && j2.size() <= i) {
                    //compareJsonIterator((JSONObject) j1.get(i), null, i, path, clazz, map);
                    compareJsonIterator((JSONObject) j1.get(i), null, sort, i + 1, names, path, clazz, data);
                } else if (j2.size() > i && j1.size() <= i) {
                    //compareJsonIterator(null, (JSONObject) j2.get(i), i, path, clazz, map);
                    compareJsonIterator(null, (JSONObject) j2.get(i), sort, i + 1, names, path, clazz, data);
                }
            }
            //data.put(key, map);
        } else if (json1 == null && json2 != null && json2.get(key) instanceof JSONArray) {
            //Map<String, Object> map = new HashMap<>();
            JSONArray j2 = ((JSONArray) json2.get(key));
            int jsCount2 = j2.size();
            int count = jsCount2;
            for (int i = 0; i < count; i++) {
                //compareJsonIterator(null, (JSONObject) j2.get(i), i, path, clazz, map);
                compareJsonIterator(null, (JSONObject) j2.get(i), sort, i + 1, names, path, clazz, data);
            }
            //data.put(key, map);
        } else if (json1 != null && json2 == null && json1.get(key) instanceof JSONArray) {
            //Map<String, Object> map = new HashMap<>();
            JSONArray j1 = ((JSONArray) json1.get(key));
            int jsCount1 = j1.size();
            int count = jsCount1;
            for (int i = 0; i < count; i++) {
                //compareJsonIterator((JSONObject) j1.get(i), null, i, path, clazz, map);
                compareJsonIterator((JSONObject) j1.get(i), null, sort, i + 1, names, path, clazz, data);
            }
            //data.put(key, map);
        } else if (json1 != null && json2 != null && json1.get(key) == null && json2.get(key) instanceof JSONArray) {
            //Map<String, Object> map = new HashMap<>();
            JSONArray j2 = ((JSONArray) json2.get(key));
            int jsCount2 = j2.size();
            int count = jsCount2;
            for (int i = 0; i < count; i++) {
                //compareJsonIterator(null, (JSONObject) j2.get(i), i, path, clazz, map);
                compareJsonIterator(null, (JSONObject) j2.get(i), sort, i + 1, names, path, clazz, data);
            }
            //data.put(key, map);
        } else if (json1 != null && json2 != null && json1.get(key) instanceof JSONArray && json2.get(key) == null) {
            //Map<String, Object> map = new HashMap<>();
            JSONArray j1 = ((JSONArray) json1.get(key));
            int jsCount1 = j1.size();
            int count = jsCount1;
            for (int i = 0; i < count; i++) {
                //compareJsonIterator((JSONObject) j1.get(i), null, i, path, clazz, map);
                compareJsonIterator((JSONObject) j1.get(i), null, sort, i + 1, names, path, clazz, data);
            }
            //data.put(key, map);
        } else {
            compareJsonOut(json1, json2, sort, index, names, path, clazz, key, data);
        }
    }

    private static void compareJsonOut(JSONObject json1, JSONObject json2, Integer sort, Integer index, String names, String path, Class clazz, String key, Map<String, Object> data) {
        if (json1 == null && json2 != null && json2.containsKey(key)) {
            compareJsonOut("", json2.get(key), ADD, sort, index, names, path, clazz, key, data);
        } else if (json1 != null && json2 == null && json1.containsKey(key)) {
            compareJsonOut(json1.get(key), "", DELETE, sort, index, names, path, clazz, key, data);
        } else if (json1 != null && json2 != null) {
            if (!json1.containsKey(key) && json2.containsKey(key)) {
                compareJsonOut("", json2.get(key), ADD, sort, index, names, path, clazz, key, data);
            } else if (json1.containsKey(key) && !json2.containsKey(key)) {
                compareJsonOut(json1.get(key), "", DELETE, sort, index, names, path, clazz, key, data);
            } else if (json1.containsKey(key) && json2.containsKey(key)) {
                if (StringUtils.isEmpty(json1.get(key)) && !StringUtils.isEmpty(json2.get(key))) {
                    compareJsonOut("", json2.get(key), ADD, sort, index, names, path, clazz, key, data);
                } else if (!StringUtils.isEmpty(json1.get(key)) && StringUtils.isEmpty(json2.get(key))) {
                    compareJsonOut(json1.get(key), "", DELETE, sort, index, names, path, clazz, key, data);
                } else if (!StringUtils.isEmpty(json1.get(key)) && !StringUtils.isEmpty(json2.get(key)) && !json1.get(key).equals(json2.get(key))) {
                    compareJsonOut(json1.get(key), json2.get(key), UPDATE, sort, index, names, path, clazz, key, data);
                }
            }
        }
    }

    /**
     * 封装对比结果
     * @param before
     * @param after
     * @param action
     * @param sort
     * @param path
     * @param clazz
     * @param key
     * @param data
     */
    private static void compareJsonOut(Object before, Object after, String action, Integer sort, Integer index, String names, String path, Class clazz, String key, Map<String, Object> data) {
        //String name = getFieldNamePath(sort, path, clazz);
        CompareVo map = new CompareVo();
        map.setBefore(before);
        map.setAfter(after);
        map.setAction(action);
        map.setKey(key);
        map.setPath(path);
        map.setName(names);
        //map.setSort(getSort(path.substring(0, path.lastIndexOf("."))) + sort + index);
        map.setSort(sort);
        if (!StringUtils.isEmpty(names)) {
            if (!StringUtils.isEmpty(before) || !StringUtils.isEmpty(after)) {
                data.put(path, map);
            }
        }
    }

    /**
     * 获取注解字段描述
     * @param path
     * @return
     */
    private static String getFieldNamePath(Integer index, String path, Class<?> clazz) {
        StringBuilder name = new StringBuilder();
        String[] keys = path.replaceAll("\\[\\d*?]", "").split("\\.");
        for (int i = 1; i < keys.length; i++) {
            if (clazz != null) {
                //父节点描述
                //name.append(getFieldName(clazz, keys[i - 1]));

                Field field = getField(clazz, keys[i - 1]);
                if (field != null) {
                    try {
                        clazz = Class.forName(field.getGenericType() instanceof ParameterizedType ? ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].getTypeName() : field.getType().getName());
                    } catch (ClassNotFoundException e) {

                    }
                }
                String key = getFieldName(clazz, keys[i]);
                if (StringUtils.isEmpty(key)) {
                    return null;
                }
                if (!StringUtils.isEmpty(name.toString())) {
                    name.append(SPLIT);
                }
                name.append(key);
            }
        }
        if (index != null && index != 0) {
            name.insert(name.lastIndexOf(SPLIT),String.format("[%s]", index));
        }
        return name.toString();
    }

    /**
     * 搜索字段
     * @param clazz
     * @param key
     * @return
     */
    private static Field getField(Class<?> clazz, String key) {
        Field field = null;
        Field[] fields = clazz.getDeclaredFields();
        for (Field item : fields) {
            if (item.getName().equals(key)) {
                field = item;
                break;
            }
        }
        return field;
    }


    /**
     * 获取注释
     * @param clazz
     * @param key
     * @return
     */
    private static String getFieldName(Class<?> clazz, String key) {
        Field field = getField(clazz, key);
        if (field == null) {
            if (clazz.getSuperclass() == java.lang.Object.class) {
                return null;
            }
            return getFieldName(clazz.getSuperclass(), key);
        } else {
            CompareProperty annotation = field.getAnnotation(CompareProperty.class);
            if (annotation == null) {
                return null;
            } else {
                return annotation.value();
            }
        }
    }


    public static void main(String[] args) {
        CompareJsonUtils.User u = new CompareJsonUtils.User();
        CompareJsonUtils.User1 uu1 = new CompareJsonUtils.User1();
        uu1.setAge(12);
        uu1.setName("123");
        uu1.setNum(BigDecimal.ONE);
        u.setUser1(uu1);

        CompareJsonUtils.User u1 = new CompareJsonUtils.User();
        CompareJsonUtils.User1 uu2 = new CompareJsonUtils.User1();
        uu2.setAge(15);
        uu2.setName("1234");
        uu2.setNum(BigDecimal.ONE);
        u1.setUser1(uu2);

        JSONObject j1 = JSON.parseObject(JSON.toJSONString(u, SerializerFeature.WriteMapNullValue));
        JSONObject j2 = JSON.parseObject(JSON.toJSONString(u1, SerializerFeature.WriteMapNullValue));
        List<CompareVo> compareVos = CompareJsonUtils.compareJsonIterator(j1, j2, "", User.class);
        System.out.println(JSON.toJSONString(compareVos));
    }


    @Data
    public static class User {
        @CompareProperty("user1")
        private User1 user1;
    }

    @Data
    public static class User1 {
        @CompareProperty("名称")
        private String name;
        @CompareProperty("年龄")
        private Integer age;
        @CompareProperty("数量")
        private BigDecimal num;
    }
}
